Skip to content

feat(secret): add --shared-with-me list mode for recipient discovery - #153

Open
c1-squire-dev[bot] wants to merge 3 commits into
mainfrom
highb/vending/shared-secret-list
Open

feat(secret): add --shared-with-me list mode for recipient discovery#153
c1-squire-dev[bot] wants to merge 3 commits into
mainfrom
highb/vending/shared-secret-list

Conversation

@c1-squire-dev

@c1-squire-dev c1-squire-dev Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds cone secret list --shared-with-me, the recipient-discovery path Phase 1 needs: a recipient discovers secrets shared with them and can then view one by its returned vault ID using the existing secret view.

  • Calls the caller-bound POST /api/v1/search/secrets/shared_with_me operation (PaperSecret.SearchSecretsSharedWithMe) through the generated SDK — continuing the architecture from Replace PaperSecret bridge with generated SDK #148; no handwritten bridge.
  • Default secret list behavior, flags, help, and output are unchanged (creator list via SearchMySecrets).
  • The new mode preserves pagination (follows next_page_token to exhaustion) and --query/--status/--type filters across pages.
  • Enforces the endpoint contract: --page-size max 100, --query max 256 chars, no user_id/sort_by/sharing_mode ever sent; an explicit --sharing-mode is rejected with a clear error instead of silently dropped.
  • include_own defaults to false (the endpoint default), opt-in via --include-own (rejected without --shared-with-me).
  • Generalizes the SDK 4XX/5XX error mapping (mapPaperSecretCreateErrormapPaperSecretError) so HTTP failures surface as cone HTTPError uniformly.
  • Output reuses the existing metadata columns (vault ID, display name, type, status, view counts) — no content fetch.

Dependency note

conductorone-sdk-go is pinned to v1.29.1-0.20260905002051-ef0d92d9c5f2 — the speakeasy-sdk-regen-1784593459 branch head (conductorone-sdk-go PR #117) generated by the established nightly Speakeasy workflow from the refreshed canonical OpenAPI input, which now includes the shared_with_me route. Once #117 merges and the v1.29.1 tag publishes, re-pin to the tag (mechanical go get + go mod vendor).

Tests

  • default list selects the creator endpoint; --shared-with-me selects the shared endpoint and never sends userId/sortBy/sharingMode (asserted on the wire via httptest)
  • two-page response fully consumed with filters preserved and pageToken threaded
  • page-size > 100 and query > 256 rejected; explicit --sharing-mode rejected; --include-own default/opt-in
  • HTTP error mapping (403 body) and context cancellation propagate
  • go test ./..., go vet ./..., golangci-lint (0 issues) all pass locally

highb and others added 2 commits September 5, 2026 00:29
Add cone secret list --shared-with-me, calling the caller-bound
POST /api/v1/search/secrets/shared_with_me operation through the
generated SDK (PaperSecret.SearchSecretsSharedWithMe). The default
creator list (SearchMySecrets), its flags, help, and output stay
unchanged; the new mode preserves pagination, query/status/type
filters, enforces the endpoint's page_size<=100 and query<=256
limits, defaults include_own=false (opt-in via --include-own), and
rejects an explicit --sharing-mode filter instead of silently
dropping it, since the endpoint accepts no user_id, sort_by, or
sharing_mode. Generalize the SDK-error-to-HTTPError mapping
(mapPaperSecretCreateError -> mapPaperSecretError) so the shared
search reports HTTP failures with the same shape as create.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
…redWithMe build

Pin github.com/conductorone/conductorone-sdk-go to
v1.29.1-0.20260905002051-ef0d92d9c5f2 (the speakeasy-sdk-regen
branch head carrying the generated PaperSecret.SearchSecretsSharedWithMe
operation and its request/response models) and re-vendor. This is the
established SDK generation output from the refreshed canonical OpenAPI
input (insulator now serves the C1 main canonical spec including the
shared_with_me route); no generated file is hand-edited. Re-pin to the
v1.29.1 tag once conductorone-sdk-go PR #117 merges and publishes.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>
Comment thread go.mod
filippo.io/age v1.3.1
github.com/conductorone/baton-sdk v0.3.17
github.com/conductorone/conductorone-sdk-go v1.29.0
github.com/conductorone/conductorone-sdk-go v1.29.1-0.20260905002051-ef0d92d9c5f2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: This pins conductorone-sdk-go to a pseudo-version on the unmerged speakeasy-sdk-regen-1784593459 branch rather than a published tag. Because that branch head is not reachable from any released ref, a force-push or branch deletion breaks go mod download/go mod verify and any non-vendored build (GOFLAGS=-mod=mod), and a release cut from this commit would ship an unreviewed pre-release SDK. Worth gating merge on the v1.29.1 tag landing so the re-pin is done here rather than as a follow-up. (confidence: high)

Comment thread cmd/cone/secret.go
// or sharing-mode filter, so those incompatibilities are rejected here rather
// than silently dropped.
func buildSearchSecretsSharedWithMeRequest(v *viper.Viper, cmd *cobra.Command) (*shared.PaperSecretServiceSearchSecretsSharedWithMeRequest, error) {
if cmd.Flags().Changed(secretSharingFlag) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: The incompatibility guards use cmd.Flags().Changed(...), but every value in this file is read through viper, which also resolves CONE_SHARING_MODE/CONE_INCLUDE_OWN env vars and profiles.<name>.sharing-mode config keys (see getSubViperForProfile in config.go). A user who sets sharing-mode via env or profile config gets it silently dropped in --shared-with-me mode instead of the clear error this is meant to produce, and include-own set the same way is silently ignored at line 646 while still being honored by v.GetBool(includeOwnFlag) at line 751. Consider gating on v.GetString(secretSharingFlag) != allFilter / v.GetBool(includeOwnFlag) so the guard matches how the values are actually read. (confidence: high)

Comment thread cmd/cone/secret.go Outdated
PageSize: &pageSize,
}
if query := strings.TrimSpace(v.GetString(queryFlag)); query != "" {
if len(query) > 256 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: len(query) counts bytes, not characters. If the endpoint's limit is 256 characters, a valid non-ASCII query (e.g. 100 CJK characters = 300 bytes) is rejected client-side before it ever reaches the API. Use utf8.RuneCountInString(query) > 256 if the contract is character-based. (confidence: medium — depends on whether the server counts bytes or runes)

Comment thread pkg/client/secret.go
Comment on lines +304 to +320
for {
resp, err := c.sdk.PaperSecret.SearchSecretsSharedWithMe(ctx, req)
if err != nil {
return nil, mapPaperSecretError(err)
}
if err := NewHTTPError(resp.RawResponse); err != nil {
return nil, err
}
if resp.PaperSecretServiceSearchResponse != nil {
out = append(out, resp.PaperSecretServiceSearchResponse.List...)
token := StringFromPtr(resp.PaperSecretServiceSearchResponse.NextPageToken)
if token != "" {
req.PageToken = &token
continue
}
}
return out, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: Two things about this loop, both inherited from SearchMySecrets but now on a newly exported C1Client method. First, it mutates the caller's request in place (req.PageToken = &token), so a caller who reuses a request value gets a stale page token on the next call — copying req locally before looping would keep the method side-effect free. Second, the loop is unbounded: a server that keeps returning the same non-empty nextPageToken spins forever while out grows without limit. A page cap or a check that the token actually changed would bound it. (confidence: medium)

Comment thread cmd/cone/secret_test.go Outdated
Comment on lines +653 to +664
// secretListRunForTest executes the same flag-routing core secretListRun uses,
// against the harness, without the authenticated cmdContext.
func secretListRunForTest(ctx context.Context, h *sharedListHarness, v *viper.Viper, cmd *cobra.Command) error {
if v.GetBool(sharedWithMeFlag) {
return secretListSharedWithMeRun(ctx, h, v, cmd)
}
req, err := buildSearchMySecretsRequest(v)
if err != nil {
return err
}
_, err = h.SearchMySecrets(ctx, req)
return err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Suggestion: secretListRunForTest re-implements secretListRun's routing rather than exercising it, so the tests validate a copy that can drift from production. Concretely, the --include-own requires --shared-with-me guard (secret.go:646-648) has no coverage at all because this copy omits it. Extracting the post-cmdContext body of secretListRun into a helper that takes (ctx, creator, sharer, v, cmd) and calling that from both places would close the gap. (confidence: high)

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

General PR Review: feat(secret): add --shared-with-me list mode for recipient discovery

Blocking Issues: 0 | Suggestions: 3 | Threads Resolved: 0
Criteria: Criteria status: none loaded - .claude/skills/ci-review.md was not found at trusted base dd29af4a7260.
Review mode: incremental since a6dc1257
View review run

Review Summary

The new commit does two things: it switches the --query limit on the shared-with-me path from len() to utf8.RuneCountInString(), and it removes the test-only secretListRunForTest copy of the routing logic by extracting the real runSecretList core (behind a new secretLister interface) that both secretListRun and the test harness now call — with new tests covering the 256/257 code-point boundary, the --include-own requires --shared-with-me guard, and the creator path's distinct contract. Both of those prior suggestions are addressed; no new issues were found in the incremental diff. The full PR diff was also re-scanned for security and correctness, including go.mod/go.sum and the vendored SDK bump (v1.29.0v1.29.1-0.20260905002051-ef0d92d9c5f2, additive regen, vendor/modules.txt consistent, go.sum touching only that module); the incremental artifact reported no dropped paths and no truncation. The three suggestions below are carried over from the previous review and are still open in the current tree.

Security Issues

None found.

Correctness Issues

None found.

Suggestions

  • go.mod:15 — (carried over) SDK still pinned to a pseudo-version on the unmerged speakeasy-sdk-regen-1784593459 branch rather than a published v1.29.1 tag; branch deletion or force-push breaks go mod download and non-vendored builds.
  • cmd/cone/secret.go:662,740 — (carried over) the --include-own and --sharing-mode incompatibility guards still use cmd.Flags().Changed() while the values are consumed through viper, so CONE_INCLUDE_OWN / CONE_SHARING_MODE and profile-config values bypass the validation.
  • pkg/client/secret.go:304-320 — (carried over) SearchSecretsSharedWithMe mutates the caller-supplied request in place and the page loop is unbounded; a server returning a constant nextPageToken loops forever with unbounded memory growth.
Prompt for AI agents
Verify each finding against the current code and only fix it if needed.

## Suggestions

In `go.mod`:
- Around line 15: `github.com/conductorone/conductorone-sdk-go` is still pinned to
  `v1.29.1-0.20260905002051-ef0d92d9c5f2`, a pseudo-version pointing at the head of the
  unmerged `speakeasy-sdk-regen-1784593459` branch (conductorone-sdk-go PR 117). That
  commit is not reachable from any released ref, so a force-push or branch deletion
  breaks `go mod download`/`go mod verify` and any build with `GOFLAGS=-mod=mod`. Once
  the `v1.29.1` tag publishes, re-pin with
  `go get github.com/conductorone/conductorone-sdk-go@v1.29.1` followed by
  `go mod tidy -v && go mod vendor` before merging.

In `cmd/cone/secret.go`:
- Around lines 662 and 740: the guard rejecting `--include-own` without
  `--shared-with-me` (line 662) and the guard rejecting `--sharing-mode` together with
  `--shared-with-me` (line 740) both test `cmd.Flags().Changed(...)`, which only sees
  values passed on the command line. Every other value in this file is read through
  viper, and `getSubViperForProfile` in `cmd/cone/config.go` enables `AutomaticEnv` with
  the `CONE` prefix plus a dash-to-underscore key replacer and profile config lookup. So
  `CONE_SHARING_MODE=internal` or a `profiles.<name>.sharing-mode` key is silently
  dropped in shared-with-me mode instead of erroring, and `CONE_INCLUDE_OWN=true` is
  silently ignored on the creator path while still being honored by
  `v.GetBool(includeOwnFlag)` at line 767. Change both guards to read through viper:
  reject when `v.GetString(secretSharingFlag)` is neither empty nor `allFilter`, and
  reject when `v.GetBool(includeOwnFlag)` is true without `--shared-with-me`. Add a test
  that sets these via `v.Set(...)` rather than `cmd.Flags().Set(...)` so the harness
  actually distinguishes the two paths.

In `pkg/client/secret.go`:
- Around lines 304-320: `SearchSecretsSharedWithMe` mutates the caller-supplied request
  in place via `req.PageToken = &token`, so a caller reusing the same request value for a
  second listing starts from a stale page token. Copy the request into a local
  (`page := *req`) before the loop and pass `&page` to the SDK. Separately, the loop has
  no bound: a server that keeps returning the same non-empty `nextPageToken` spins
  forever while `out` grows without limit. Stop when the new token equals the previous
  one and/or cap the number of pages followed. `SearchMySecrets` (lines 258-270) and
  `SearchSecretAuditEvents` (lines 330-342) have the same shape if you want the three to
  stay consistent.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

…n routing

Address review feedback on PR #153:

- The shared-with-me query limit now counts Unicode code points
  (utf8.RuneCountInString) matching the server's protoc-gen-validate
  max_len:256 semantics, not UTF-8 bytes. A 256-character multibyte
  query (512 bytes) passes; 257 characters fails. Regression tests
  cover the multibyte boundary both sides.

- Routing tests now drive the production runSecretList core (extracted
  from secretListRun so the branch selection, include-own-without-
  shared-with-me rejection, and creator-path contract are the exact
  code the CLI executes) instead of a duplicated dispatch. New tests
  pin the include-own guard and the creator path's page-size-1000 /
  created-desc sort / sharing-mode-allowed contract.

Co-authored-by: c1-squire-dev[bot] <c1-squire-dev[bot]@users.noreply.github.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant